package com.ronsai.umtrack.api.proxy;

import android.text.TextUtils;

import com.ronsai.umtrack.MyApp;
import com.ronsai.umtrack.api.Api;
import com.ronsai.umtrack.api.DefaultObserver;
import com.ronsai.umtrack.api.IGlobalManager;
import com.ronsai.umtrack.api.exception.RefreshTokenExpiredException;
import com.ronsai.umtrack.api.exception.TokenEmptyException;
import com.ronsai.umtrack.api.exception.TokenExpiredException;
import com.ronsai.umtrack.entity.Login;
import com.ronsai.umtrack.util.SPUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.functions.Function;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.Part;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;

/**
 * 代理：token过期自动刷新并重新调用接口
 */
public class ProxyHandler implements InvocationHandler {

    private final static String TAG = "Token_Proxy";

    private final static String TOKEN = "token";

    private final static int REFRESH_TOKEN_VALID_TIME = 30;
    private static long tokenChangedTime = 0;
    private Throwable mRefreshTokenError = null;
    private boolean mIsTokenNeedRefresh;

    private Object mProxyObject;
    private IGlobalManager mGlobalManager;

    public ProxyHandler(Object proxyObject, IGlobalManager globalManager) {
        mProxyObject = proxyObject;
        mGlobalManager = globalManager;
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        return Observable.just(true).flatMap((Function<Object, ObservableSource<?>>) o -> {
            try {
                try {
                    if (mIsTokenNeedRefresh) {
                        updateMethodToken(method, args);
                    }
                    return (Observable<?>) method.invoke(mProxyObject, args);
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }).retryWhen(observable -> observable.flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
            if (throwable instanceof TokenEmptyException) {//token为空
                mGlobalManager.logout();
                return Observable.error(throwable);
            } else if (throwable instanceof TokenExpiredException) {// token过期
                return refreshTokenWhenTokenInvalid();
            } else if (throwable instanceof RefreshTokenExpiredException) {
                // RefreshToken过期，执行退出登录的操作。
                mGlobalManager.logout();
                return Observable.error(throwable);
            }
            return Observable.error(throwable);
        }));
    }

    /**
     * Refresh the token when the current token is invalid.
     *
     * @return Observable
     */
    private Observable<?> refreshTokenWhenTokenInvalid() {
        synchronized (ProxyHandler.class) {
            // Have refreshed the token successfully in the valid time.
            if (new Date().getTime() - tokenChangedTime < REFRESH_TOKEN_VALID_TIME) {
                mIsTokenNeedRefresh = true;
                return Observable.just(true);
            } else {
                // call the refresh token api.
                String refreshToken = SPUtils.getInstance().getString("refreshToken");
                Api.getApiService().refreshToken(refreshToken).subscribe(new DefaultObserver<Login>() {
                    @Override
                    public void onSuccess(Login login) {
                        if (login != null) {
                            mIsTokenNeedRefresh = true;
                            tokenChangedTime = new Date().getTime();
                            mGlobalManager.refreshToken(login);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        super.onError(e);
                        mRefreshTokenError = e;
                    }
                });
                if (mRefreshTokenError != null) {
                    return Observable.error(mRefreshTokenError);
                } else {
                    return Observable.just(true);
                }
            }
        }
    }

    private void updateMethodToken(Method method, Object[] args) {
        String token = MyApp.getToken();
        if (mIsTokenNeedRefresh && !TextUtils.isEmpty(token)) {
            Annotation[][] annotationsArray = method.getParameterAnnotations();
            Annotation[] annotations;
            if (annotationsArray != null && annotationsArray.length > 0) {
                for (int i = 0; i < annotationsArray.length; i++) {
                    annotations = annotationsArray[i];
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof FieldMap || annotation instanceof QueryMap) {// 以Map方式提交表单
                            if (args[i] instanceof Map)
                                ((Map<String, Object>) args[i]).put(TOKEN, token);
                        } else if (annotation instanceof Query) {
                            if (TOKEN.equals(((Query) annotation).value())) {
                                args[i] = token;
                            }
                        } else if (annotation instanceof Field) {
                            if (TOKEN.equals(((Field) annotation).value())) {
                                args[i] = token;
                            }
                        } else if (annotation instanceof Part) {   //  上传文件
                            if (TOKEN.equals(((Part) annotation).value())) {
                                RequestBody tokenBody = RequestBody.create(MediaType.parse("multipart/form-data"), token);
                                args[i] = tokenBody;
                            }
                        }
                    }
                }
            }
            mIsTokenNeedRefresh = false;
        }
    }
}
